package org.acghub.mtdb.core.log;

import org.acghub.mtdb.core.*;

import java.util.*;

public abstract class LogAbstractMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Verifyable {

    protected LogKey logkey;

    protected LogAbstractMap<K, V> root;

    protected Map<K, V> wrapped;

    protected Runnable verify;
    private EntrySet es;
    private KeySet ks;
    private Values vs;

    public LogAbstractMap(LogKey logkey, Map<K, V> wrapped) {
        this.logkey = logkey;
        this.wrapped = wrapped;
    }

    protected LogAbstractMap(LogAbstractMap<K, V> root, Map<K, V> wrapped) {
        this.root = root;
        this.wrapped = wrapped;
    }

    @Override
    public Runnable getVerify() {
        return verify;
    }

    @Override
    public void setVerify(Runnable verify) {
        this.verify = verify;
    }

    @Override
    public final void verify() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int size() {
        return wrapped().size();
    }

    @Override
    public boolean isEmpty() {
        return wrapped().isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return wrapped().containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return wrapped().containsValue(value);
    }

    @Override
    public V get(Object key) {
        return wrapped().get(key);
    }

    @Override
    public V put(K key, V value) {
        if (Objects.isNull(key) || Objects.isNull(value)) {
            throw new NullPointerException(String.format("key=%s, value=%s", key, value));
        }
        onChange();
        V origin = wrapped().put(key, value);
        onPut(key, origin, value);
        return origin;
    }

    @Override
    public V remove(Object key) {
        onChange();
        V origin = wrapped().remove(key);
        if (origin != null) {
            onRemove((K) key, origin);
        }
        return origin;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        if (m != null && !m.isEmpty()) {
            m.forEach(this::put);
        }
    }

    @Override
    public void clear() {
        onChange();
        wrapped().forEach(this::onRemove);
        wrapped().clear();
    }

    @Override
    public Set<K> keySet() {
        return ks != null ? ks : (ks = new KeySet());
    }

    @Override
    public Collection<V> values() {
        return vs != null ? vs : (vs = new Values());
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        return es != null ? es : (es = new EntrySet());
    }

    protected final MapLog log() {
        SavePoint sp = Transaction.currentSavepoint();
        MapLog log = sp.get(logkey);
        if (null == log) {
            log = createLog();
            log.setVerify(verify);
            sp.add(logkey, log);
        }
        return log;
    }


    protected final void onChange() {
        if (root != null) {
            root.onChange();
        } else {
            log().onChange();
        }
    }

    protected final void onPut(K key, V origin, V value) {
        if (root != null) {
            root.onPut(key, origin, value);
        } else {
            log().onPut(key, origin, value);
        }
    }

    protected final void onRemove(K key, V origin) {
        if (root != null) {
            root.onRemove(key, origin);
        } else {
            log().onRemove(key, origin);
        }
    }

    protected MapLog createLog() {
        return new DefaultMapLog();
    }

    protected <W extends Map<K, V>> W wrapped() {
        return (W) wrapped;
    }

    private Iterator<K> newKeyIterator() {
        return new LogEntryIte<K>() {
            @Override
            public K next() {
                return nextEntry().getKey();
            }
        };
    }

    private Iterator<V> newValueIterator() {
        return new LogEntryIte<V>() {
            @Override
            public V next() {
                return nextEntry().getValue();
            }
        };
    }

    private Iterator<Entry<K, V>> newEntryIterator() {
        return new LogEntryIte<Entry<K, V>>() {
            @Override
            public Entry<K, V> next() {
                return nextEntry();
            }
        };
    }

    // package private
    protected abstract class LogEntryIte<E> implements Iterator<E> {
        private final Iterator<Entry<K, V>> it;
        private LogEntry current;

        public LogEntryIte() {
            this(wrapped.entrySet().iterator());
        }

        public LogEntryIte(Iterator<Entry<K, V>> it) {
            this.it = it;
        }

        @Override
        public void remove() {
            it.remove();
            log().onRemove(current.getKey(), current.getValue());
        }

        @Override
        public boolean hasNext() {
            return it.hasNext();
        }

        Entry<K, V> nextEntry() {
            return current = new LogEntry(it.next());
        }
    }

    protected class LogEntry implements Map.Entry<K, V> {

        protected final Map.Entry<K, V> e;

        public LogEntry(Map.Entry<K, V> e) {
            this.e = e;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LogAbstractMap<?, ?>.LogEntry) {
                return Objects.equals(e, ((LogAbstractMap<?, ?>.LogEntry) obj).e);
            }
            return Objects.equals(e, obj);
        }

        @Override
        public K getKey() {
            return e.getKey();
        }

        @Override
        public V getValue() {
            return e.getValue();
        }

        @Override
        public int hashCode() {
            return e.hashCode();
        }

        @Override
        public V setValue(V value) {
            if (null == value) {
                throw new NullPointerException("value=" + value);
            }
            V origin = e.setValue(value);
            log().onPut(e.getKey(), origin, value);
            return origin;
        }
    }

    protected abstract class MapLog implements Log, Note, Verifyable {

        protected Runnable verify;

        @Override
        public Runnable getVerify() {
            return verify;
        }

        @Override
        public void setVerify(Runnable verify) {
            this.verify = verify;
        }

        protected void onChange() {
            verify();
        }

        abstract void onRemove(K key, V origin);

        abstract void onPut(K key, V origin, V value);

    }

    protected class DefaultMapLog extends MapLog {

        // 被put调用新增的键值
        private final Set<K> added = new HashSet<>();

        // 被put调用替换掉的键值，其中值(value)为替换前的。
        private final Map<K, V> replaced = new HashMap<>();

        // 被remove调用删除的键值，其中值(key, value)为删除前的。
        private final Map<K, V> removed = new HashMap<>();


        // map 本身的改变，不包括 List<V> changed。
        public final boolean isChanged() {
            return !added.isEmpty() || !removed.isEmpty() || !replaced.isEmpty();
        }

        @Override
        public final void clear() {
            this.added.clear();
            this.removed.clear();
            this.replaced.clear();
        }

        private void remove0(K key, V value) {
            if (!this.added.remove(key)) {
                // 如果是覆盖的，此时v才是最早的，参数value是replace时的。
                V v = this.replaced.remove(key);
                this.removed.put(key, v == null ? value : v);
            }
        }

        private void put0(K key, V origin) {
            // 覆盖新增条目。仍然保留新增的日志。
            if (this.added.contains(key)) {
                return;
            }

            // 判断是否加入删除过。
            V v = this.removed.remove(key);
            if (null != v) {
                // 删除以后再次加入，保留第一次是删除产生的日志内容，日志转移到覆盖日志中。
                // 此时条目存在。不处理再次加入的value和以前删除的value是否一样。
                this.replaced.put(key, v);
                return;
            }
            if (this.replaced.containsKey(key)) {
                return; // 再次覆盖条目。保留第一次的日志。
            }

            // 第一次覆盖。此时根据origin产生added或者replaced日志。
            if (null == origin) {
                this.added.add(key);
            } else {
                this.replaced.put(key, origin);
            }
        }

        @Override
        public String toString() {
            return "added=" + added + " replaced=" + replaced + " removed=" + removed;
        }

        @Override
        public void commit() {
            if (isChanged()) {
                LogNotify.notify(logkey, this);
            }
        }

        @Override
        public void rollback() {
            wrapped.keySet().removeAll(added);
            wrapped.putAll(removed);
            wrapped.putAll(removed);
            clear();
        }

        @Override
        void onRemove(K key, V origin) {
            remove0(key, origin);
            Logs.link(origin, null, null);
        }

        @Override
        void onPut(K key, V origin, V value) {
            put0(key, origin);
            if (origin != null) {
                Logs.link(origin, null, null);
            }
            Logs.link(value, logkey.getBean(), logkey.getVarname());
        }
    }

    protected class KeySet extends AbstractSet<K> {
        @Override
        public Iterator<K> iterator() {
            return LogAbstractMap.this.newKeyIterator();
        }

        @Override
        public int size() {
            return LogAbstractMap.this.size();
        }

        @Override
        public boolean contains(Object o) {
            return LogAbstractMap.this.containsKey(o);
        }

        @Override
        public boolean remove(Object o) {
            return LogAbstractMap.this.remove(o) != null;
        }

        @Override
        public void clear() {
            LogAbstractMap.this.clear();
        }
    }

    protected class Values extends AbstractCollection<V> {
        @Override
        public Iterator<V> iterator() {
            return newValueIterator();
        }

        @Override
        public int size() {
            return LogAbstractMap.this.size();
        }

        @Override
        public boolean contains(Object o) {
            return LogAbstractMap.this.containsValue(o);
        }

        @Override
        public void clear() {
            LogAbstractMap.this.clear();
        }
    }


    protected class EntrySet extends AbstractSet<Map.Entry<K, V>> {
        @Override
        public Iterator<Map.Entry<K, V>> iterator() {
            return LogAbstractMap.this.newEntryIterator();
        }

        @Override
        public boolean contains(Object o) {
            return LogAbstractMap.this.wrapped.entrySet().contains(o);
        }

        @Override
        public int size() {
            return LogAbstractMap.this.wrapped.size();
        }

        @Override
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();

        }

        @Override
        public void clear() {
            LogAbstractMap.this.clear();
        }
    }

}
